package com.cooper.scraper;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Set;

import com.cooper.scraper.CustomFixExecutorService.ThreadPoolStatusListener;

import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class ThreadPoolDownloadPublishRunner implements DownloadPublishRunner {

	public static final int MESSAGE_DOWNLOAD_COMPLETE = 1;
	public static final int MESSAGE_DOWNLOAD_FAIL = 2;
	public static final int MESSAGE_DOWNLOAD_CANCEL = 3;

	// protected DownloadParams params;

	protected ResultPool resultPool;

	// private RunnerStatusListener statusListener;

	// private Map<String, Future<?>> requests = new HashMap<String,
	// Future<?>>();

	// private ExecutorService threadPool;

	private List<RunnerSubscribe> subscribes = new ArrayList<RunnerSubscribe>();

	protected ThreadPoolDownloadPublishRunner() {
		super();
		init();
	}

	private void init() {
		resultPool = new ResultPool(handler);
	}

	private Handler handler = new Handler() {

		@Override
		public synchronized void handleMessage(Message msg) {
			List<RunnerSubscribe> tempSubscribes = new ArrayList<RunnerSubscribe>(
					subscribes);
			Set<String> keys;
			switch (msg.what) {
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_COMPLETE:
				keys = resultPool.completeKeys();
				for (String key : keys) {
					Object resultData = resultPool.getResult(key);
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadComplete(key, resultData);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader",
										"cancel subscribe" + e.getMessage(), e);
							}
						}
					}
				}
				break;
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_FAIL:
				keys = resultPool.failKeys();
				for (String key : keys) {
					Integer errorCode = resultPool.failErrorCode(key);
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadFail(key, errorCode);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			case ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_CANCEL:
				keys = resultPool.cancelKeys();
				for (String key : keys) {
					for (RunnerSubscribe subscribe : tempSubscribes) {
						if (subscribe.subscribed(key)) {
							try {
								subscribe.onDownloadCancel(key);
							} catch (Exception e) {
								subscribes.remove(subscribe);
								Log.e("Downloader", e.getMessage(), e);
							}
						}
					}
				}
				break;
			}

			CustomFixExecutorService.getInstance().cleanRequest();
		}
	};

	// private boolean busy = false;

	protected void run(String key, RunnerThread thread) {
		CustomFixExecutorService.getInstance().run(key, thread);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#setStatusListener(com.cooper.scraper
	 * .RunnerStatusListener)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#setStatusListener(com.cooper.scraper
	 * .CustomFixExecutorService.ThreadPoolStatusListener)
	 */
	@Override
	public void setStatusListener(ThreadPoolStatusListener statusListener) {
		CustomFixExecutorService.getInstance()
				.setStatusListener(statusListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#subscribed(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#subscribed(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	@Override
	public boolean subscribed(RunnerSubscribe subscribe) {
		return subscribes.contains(subscribe);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#subscribe(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#subscribe(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	@Override
	public void subscribe(RunnerSubscribe subscribe) {
		if (!subscribes.contains(subscribe)) {
			subscribes.remove(subscribe);
		}
		subscribes.add(subscribe);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#getTaskCount(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#getTaskCount(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	@Override
	public int getTaskCount(RunnerSubscribe subscribe) {
		CustomFixExecutorService.getInstance().cleanRequest();
		int result = 0;
		for (String key : CustomFixExecutorService.getInstance().keySet()) {
			if (subscribe.subscribed(key)) {
				result++;
			}
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe)
	 */
	@Override
	public void cancelSubscribe(RunnerSubscribe subscribe) {
		cancelSubscribe(subscribe, true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe, boolean)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancelSubscribe(com.cooper.scraper.
	 * RunnerSubscribe, boolean)
	 */
	@Override
	public void cancelSubscribe(RunnerSubscribe subscribe,
			boolean cancelDownload) {
		if (subscribes.contains(subscribe)) {
			if (cancelDownload) {
				cancel(subscribe);
			}
			subscribes.remove(subscribe);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel(java.lang.String)
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel(java.lang.String)
	 */
	@Override
	public void cancel(String key) {
		CustomFixExecutorService.getInstance().cancel(key);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel()
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.cooper.scraper.PublishRunner#cancel()
	 */
	@Override
	public void cancel() {
		CustomFixExecutorService.getInstance().cancel();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#cancel(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.cooper.scraper.PublishRunner#cancel(com.cooper.scraper.RunnerSubscribe
	 * )
	 */
	@Override
	public void cancel(RunnerSubscribe subscribe) {
		CustomFixExecutorService.getInstance().cleanRequest();
		List<String> cancelRequests = new ArrayList<String>();
		for (String key : CustomFixExecutorService.getInstance().keySet()) {
			if (subscribe.subscribed(key)) {
				cancelRequests.add(key);
			}
		}
		for (String key : cancelRequests) {
			CustomFixExecutorService.getInstance().cancel(key, false);
			Log.d("DownloaderPublishRunner",
					"cancel Download from cancel subscribe key:" + key);
		}

	}

	public void downloadBitmap(final String url, String key, final float width,
			final float height) {
		downloadBitmap(url, null, key, width, height);
	}

	public void downloadBitmap(final String url, final String referer,
			String key, final float width, final float height) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(resultPool) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = null;
					Bitmap srcImg = HttpDownload.getInstance().downloadBitmap(
							url, referer);
					if (srcImg != null) {
						result = Bitmap.createScaledBitmap(srcImg, (int) width,
								(int) height, true);
						if (srcImg != null && !srcImg.isRecycled()) {
							srcImg.recycle();
						}
					}
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	@Override
	public void downloadBitmap(final String url, String key) {
		downloadBitmap(url, null, key);
	}

	@Override
	public void downloadBitmap(final String url, final String referer,
			String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(resultPool) {
			@Override
			public void run() {
				super.run();
				try {
					Bitmap result = HttpDownload.getInstance().downloadBitmap(
							url, referer);
					complete(useKey, result);
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	@Override
	public void downloadFile(final String url, final File file, String key) {
		downloadFile(url, null, file, key);
	}

	@Override
	public void downloadFile(final String url, final String referer,
			final File file, String key) {
		final String useKey;
		if (key == null) {
			useKey = url;
		} else
			useKey = key;
		run(useKey, new RunnerThread(resultPool) {
			@Override
			public void run() {
				super.run();
				try {
					HttpDownload.getInstance().downloadFile(url, referer, file);
					complete(useKey, file.getPath());
				} catch (DownloadException e) {
					fail(useKey, e.getErrorCode());
				} catch (Exception e) {
					Log.e("Downloader", e.getMessage(), e);
					fail(useKey, DownloadException.ERROR_OTHER);
				}
			}

		});
	}

	public void destroy() {
		subscribes.clear();
		cancel();
	}

	public static class RunnerThread extends Thread {

		protected ResultPool resultPool;

		public RunnerThread(ResultPool resultPool) {
			super();
			this.resultPool = resultPool;
		}

		protected void complete(String key, Object result) {
			if (!Thread.currentThread().isInterrupted()) {
				Log.d("DownloadThread", "Download is complete:" + key);
				resultPool.putResult(key, result);
				// params.getHandler().sendEmptyMessage(
				// ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_COMPLETE);
			} else {
				Log.d("DownloadThread", "Download is Interrupted" + key);
				resultPool.putCancel(key);
				// params.getHandler().sendEmptyMessage(
				// ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_CANCEL);
			}
		}

		protected void fail(String key, int errorCode) {
			if (!Thread.currentThread().isInterrupted()) {
				Log.d("DownloadThread", "Download fail" + key);
				resultPool.putFail(key, errorCode);
				// params.getHandler().sendEmptyMessage(
				// ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_FAIL);
			} else {
				Log.d("DownloadThread", "Download is Interrupted" + key);
				resultPool.putCancel(key);
				// params.getHandler().sendEmptyMessage(
				// ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_CANCEL);
			}
		}
	}

	public static class ResultPool {

		private Map<String, Object> completePool = new HashMap<String, Object>();
		private Map<String, Integer> failPool = new HashMap<String, Integer>();
		private Set<String> cancelPool = new HashSet<String>();

		private final Lock resultPoolLock = new ReentrantLock();
		private final Lock failPoolLock = new ReentrantLock();
		private final Lock cancelPoolLock = new ReentrantLock();

		private Handler handler;

		public ResultPool(Handler handler) {
			super();
			this.handler = handler;
		}

		public void putCancel(String key) {
			cancelPoolLock.lock();
			try {
				cancelPool.add(key);
			} finally {
				cancelPoolLock.unlock();
			}
			handler.sendEmptyMessage(ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_CANCEL);
		}

		public void putResult(String key, Object result) {
			resultPoolLock.lock();
			try {
				completePool.put(key, result);
			} finally {
				resultPoolLock.unlock();
			}
			handler.sendEmptyMessage(ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_COMPLETE);
		}

		public void putFail(String key, Integer errorCode) {
			failPoolLock.lock();
			try {
				failPool.put(key, errorCode);
			} finally {
				failPoolLock.unlock();
			}
			handler.sendEmptyMessage(ThreadPoolDownloadPublishRunner.MESSAGE_DOWNLOAD_FAIL);
		}

		public Set<String> cancelKeys() {
			cancelPoolLock.lock();
			try {
				Set<String> result = new HashSet<String>(cancelPool);
				cancelPool.clear();
				return result;
			} finally {
				cancelPoolLock.unlock();
			}
		}

		public Set<String> failKeys() {
			failPoolLock.lock();
			try {
				return new HashSet<String>(failPool.keySet());
			} finally {
				failPoolLock.unlock();
			}
		}

		public Integer failErrorCode(String key) {
			failPoolLock.lock();
			try {
				Integer result = failPool.get(key);
				failPool.remove(key);
				return result;
			} finally {
				failPoolLock.unlock();
			}
		}

		public Set<String> completeKeys() {
			resultPoolLock.lock();
			try {
				return new HashSet<String>(completePool.keySet());
			} finally {
				resultPoolLock.unlock();
			}
		}

		public Object getResult(String key) {
			resultPoolLock.lock();
			try {
				Object result = completePool.get(key);
				completePool.remove(key);
				return result;
			} finally {
				resultPoolLock.unlock();
			}
		}
	}
}
